#pragma once

#include "net/libgo.h"
#include "net/defines.h"
#include "client.h"
#include "finder.h"

namespace libzq
{
	class RequestImpl
	{
	public:
		RequestImpl(const char* method, uint32_t flag) :
			_msgid(make_uuid()),
			_method(method),
			_flag(flag)
		{
			_name = format("%s:%s", method, _msgid);
			LOGT("New request [%s]", _name);
		}

	protected:
		string _msgid;	// 请求的唯一ID
		string _method; // 调用的方法名
		uint32_t _flag; // 调用标记
		string _name;
	};


	template<typename Protocol, typename Finder>
	class Request :
		public RequestImpl,
		public enable_shared_from_this<Request<Protocol, Finder>>
	{
	public:
		enum CallStatus
		{
			FAIL = -1,
			EXPIRE = -2,
			SUCCESS = 1,
			GET_RESP = 2,
		};
	public:
		Request(const char* method, uint32_t flag, shared_ptr<Client<Protocol>> client) :
			RequestImpl(method, flag),
			_retry_times(0),
			_expire_time(0),
			_chan(1),
			_status(FAIL),
			_expire_timer(nullptr),
			_client(client)
		{

		}

		shared_ptr<Request<Protocol, Finder>> exec(Protocol arg);
		Protocol await();

		shared_ptr<Request<Protocol, Finder>> retry(uint8_t retry_times);
		// 设置超时时间， 针对client超时， 但实际server端处理完成的情况， 需要请求调用方主动向server请求该次调用是否成功
		shared_ptr<Request<Protocol, Finder>> expire(uint8_t expire_time);

		bool is_expire() { return _status == EXPIRE; }
		bool is_fail() { return _status == FAIL; }

		static shared_ptr<Request<Protocol, Finder>> create(const char* service, const char* method);
	private:
		void on_call_success();
		void on_call_failed();
	private:
		//string _msgid;	// 请求的唯一ID
		//string _method; // 调用的方法名
		//uint32_t _flag; // 调用标记
		uint8_t _retry_times; // 重试次数
		uint8_t _expire_time; // 超时时间
		co_chan<Protocol> _chan; // 返回值channel
		CallStatus _status; // 调用成功?
		co_timer_id _expire_timer;
		shared_ptr<Client<Protocol>> _client;
		//string _name;
	};

	template<typename Protocol, typename Finder>
	inline shared_ptr<Request<Protocol, Finder>> Request<Protocol, Finder>::exec(Protocol arg)
	{
		if (!_client)
		{
			on_call_failed();
			return Request<Protocol, Finder>::shared_from_this();
		}
		arg.set_method(_method);
		string req_str = arg.dumps();
		MsgPacker msg(req_str, _msgid.c_str());

		msg.expire(_expire_time);
		bool success = false;
		std::tie(success, _chan) = _client->call(msg);
		success ? on_call_success() : on_call_failed();
		return Request<Protocol, Finder>::shared_from_this();
	}

	template<typename Protocol, typename Finder>
	inline Protocol Request<Protocol, Finder>::await()
	{
		Protocol ret;
		_chan >> ret;
		if (_expire_timer && !is_expire())
		{
			_status = GET_RESP;
			co_timer_block_cancel(_expire_timer);
		}
		return std::move(ret);
	}

	template<typename Protocol, typename Finder>
	inline shared_ptr<Request<Protocol, Finder>> Request<Protocol, Finder>::retry(uint8_t retry_times)
	{
		_retry_times = retry_times;
		return Request<Protocol, Finder>::shared_from_this();
	}

	template<typename Protocol, typename Finder>
	inline shared_ptr<Request<Protocol, Finder>> Request<Protocol, Finder>::expire(uint8_t expire_time)
	{
		_expire_time = expire_time;
		return Request<Protocol, Finder>::shared_from_this();
	}

	template<typename Protocol, typename Finder>
	inline shared_ptr<Request<Protocol, Finder>> Request<Protocol, Finder>::create(const char* service, const char * method)
	{
		auto client = Finder::instance().find_server(service, method);
		return make_shared<Request<Protocol, Finder>>(method, 0, client);
	}

	template<typename Protocol, typename Finder>
	inline void libzq::Request<Protocol, Finder>::on_call_success()
	{
		_status = SUCCESS;
		if (!_expire_time)
			return;
		// 超时处理
		_expire_timer = co_timer_add(std::chrono::seconds(_expire_time), [this]()
		{
			LOGW("Request [%s] expired", _name);
			Protocol ret;
			ret.set_error(-997, "Request expired");

			_status = EXPIRE;
			_client->expire(_msgid);
			_chan << ret;
		});
	}

	template<typename Protocol, typename Finder>
	inline void Request<Protocol, Finder>::on_call_failed()
	{
		_status = FAIL;
		Protocol ret;
		ret.set_error(-998, "Request failed");
		_chan << ret;
	}
}